/*  version: 20060110-2131 */
#include <syslog.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <errno.h>
#include <netdb.h>
#include <ctype.h>
#include <stdio.h>
#include <signal.h>
#include <sys/wait.h>
#include <fcntl.h>

#include <unistd.h>
#include <string.h>
#include <stddef.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <time.h>
#include <pthread.h>
#include <netinet/ip6.h>
#include <stdlib.h>

#define _IN_MAIN

#include "tunnet.h"
#include "global.h"
#include "common.h"

int neighbor_exchange(struct node *nbr);  

char * hostName=NULL;
char * userName=NULL;
char * neighborFile=NULL;
int ctrlPort=0;
char * secret=NULL;

int   nbr_ip_type=ADDR_TYPE_IPV4;
char * nbr_ip=NULL;
char * nbr_userName=NULL;
int    nbr_ctrlPort=0;
char * nbr_secret=NULL;

int usage(char *pn){
	printf("usage:%s [-d] [-h host] [-p port] [-s secret] [-S type] [-A action]\n", pn);
	exit(-1); 
}

/*********************************
**   TYPE    : ACTION
-------------+-------------------
**   service : Start/Pause /Stop   -T socks4|http|socks5 -A start|pause|stop 
**   Neighbor: ADD/LIST/Delet      -T neighbor -A add -U nbr_name -I ip_address -P ctrlPort -S secret
				   -T neighbor -A list [-U nbr_name]
**   Route   : ADD/LIST/Delet      -T route    -A list
				   -T route    -A add  -N net_addr -M mask -H next_hop
**   Cache   : ADD/LIST/Delet      -T cache    -A list
**********************************/


LOOKUP_ITEM command_types[]={
        { SRVC_TYPE_SOCKS4, "socks4"},
        { SRVC_TYPE_SOCKS5, "socks5"},
        { SRVC_TYPE_HTTP,   "http"},
        { ADMIN_NBR,        "neighbor"},
        { ADMIN_ROUTE,      "route"},
        { ADMIN_CACHE,      "cache"},
        { -1, NULL}
};

LOOKUP_ITEM action_types[]={
        { ADMIN_START, "start"},
        { ADMIN_STOP,  "stop"},
        { ADMIN_PAUSE, "pause"},
        { ADMIN_LIST,  "list"},
	{ ADMIN_ADD ,  "add"},
	{ ADMIN_DEL  , "delelte"},
	{ ADMIN_UPDATE,"update"}, 
        { -1, NULL}
};
LOOKUP_ITEM service_status[]={
        { SRVC_STAT_START, "started"},
        { SRVC_STAT_STOP,  "stopped"},
        { SRVC_STAT_PAUSE, "paused"},
        { -1, NULL}
};


int main(int argc, char ** argv){
    int oc ;
    f_debug=0;
    f_report=0;
    timeout=5;
    info_log=stderr;
    char errMsg[128];

    authMethod=METHOD_NONE;
    int i;
    int cmd_type, cmd_action;

    struct sockaddr_in saddr;
    int sock,status;
    struct node_item *np;


    cmd_type=cmd_action=-1; 
    while((oc=getopt(argc, argv,"dh:p:s:u:A:T:U:I:P:S:")) !=-1){
        switch(oc){
                case 'd':
                        f_debug=1;
                        f_report=1;
                        break;
                case 'h':
                        hostName=optarg;
                        break;
                case 'p':
                        ctrlPort=atoi(optarg);
                        break;
                case 'u':
                        userName=optarg;
                        break;
                case 's':
                        secret=optarg;
                        break;
                case 'T':
                       if(0>(cmd_type=lookup_name(command_types,optarg))){
			    printf("Unknow command type\n");
			    usage(argv[0]);
			    exit(-1);
			}
                       break;
                case 'A':
                       if(0>(cmd_action=lookup_name(action_types,optarg))){
			    printf("Unknow command action\n");
			    usage(argv[0]);
			    exit(-1);
			}
                       break;
                case 'I':
                        nbr_ip=optarg;
                        break;
                case 'P':
                        nbr_ctrlPort=atoi(optarg);
                        break;
                case 'U':
                        nbr_userName=optarg;
                        break;
                case 'S':
                        nbr_secret=optarg;
                        break;
                case ':':
                case 'v':
                case '?':
                        usage(argv[0]);
                        exit(-1);
        }//switch
	i++;
    } //while getopt

    // connect to server

        sock=socket(AF_INET, SOCK_STREAM, 0);
        if(sock <0) {
                sys_log(info_log,"Error : create socket error(exchange)\n");
                return -1;
        }
        saddr.sin_family=AF_INET;
        saddr.sin_addr.s_addr=inet_addr(hostName);
        saddr.sin_port=htons(ctrlPort);

        status=connect_nonb(sock,(struct sockaddr *)&saddr, sizeof(saddr),&timeout);
        if(status <0){
                fprintf(stderr,"Connect neighbor error.\n");
                close(sock);
                return -1; 
	}
	status=authentication(sock, userName, secret);
        if(status <0){
                debug("Authentication error in exchange(). \n");
                close(sock);
                return -1;
        }

	switch (cmd_type){
		case -1: 
			 get_services(sock);
		break;

		case SRVC_TYPE_SOCKS4:
		case SRVC_TYPE_SOCKS5:
		case SRVC_TYPE_HTTP:
		     if(cmd_action ==-1){
			fprintf(stderr, "action can not be null\n");
			close(sock);
			exit(-1);
		     }
		     status=service_control(sock, cmd_type, cmd_action);
		     printf("%s\n", lookup_code(service_status,status));
		break;

		case  ADMIN_NBR:
		     if(cmd_action==ADMIN_LIST || cmd_action==-1){
		         status=neighbor_request(sock,cmd_type,cmd_action);
		     }
		     else if (cmd_action=ADMIN_ADD)
		     {
			 if(nbr_userName==NULL || nbr_secret==NULL || nbr_ip==NULL || nbr_ctrlPort ==0){
			     fprintf(stderr, "user name, password, ipaddress, controPort can not be null\n"); 
			     break;
			 }
		         status=neighbor_add(sock,nbr_userName,nbr_secret,nbr_ip,
					nbr_ip_type, nbr_ctrlPort,errMsg);
			 debug("status=%d\n",status);
			 if(status<0) {
				printf("neighbor_add error.\n");
				break;
			 }
		     }//else if
		break;
	}
	close(sock);

    return 0;
}

/***
	 +---------+-----------+--------+------+
   C->S: |version:1|CMD_ADMIN:1|SERVICE_TYPE1|SERVICE_ACTION|
	 +---------+-----------+--------+------+

   S->C: |version:1|CMD_ADMIN:1|STATUS|	
***/
int service_control(int sock, int service_type, int service_action){
	char buf[128];
	
        buf[0]=VERSION_CTRL;
        buf[1]=CMD_ADMIN;
	buf[2]=service_type;	
	buf[3]=service_action;

	ATOMIC_OUT(sock, buf,4,"Send control Command Error\n");

	ATOMIC_IN(sock, buf,3,"Receive control Command Error\n");


	return buf[2];

}
/*
	+----1--+----1----+----1----+----1--------+-----1--+----/---+---1----+----/-+-1------+--/---+--2-----+
	|VERSION|CMD_ADMIN|ADMIN_NBR|ADMIN_NBR_ADD|U_LENGTH|userName|S_LENGTH|Secret|addrType|ipAddr|ctrlPort|
	+-------+---------+---------+-------------+--------+--------+--------+------+--------+------+--------+
*/
int neighbor_add(int sock, char *userName, char *secret, char *ipAddress, int addrType, int controlPort,
		char *outMsg){

	char *buf,*ptr;   
        in_addr_t addr_ip;
        int result;
	unsigned short ctrlPort=htons(controlPort);
    
	debug("userName=%s secret=%s ipAddress=%s addtype=%d ctrlPort=%d \n", 
		userName, secret, ipAddress, addrType, ctrlPort);

	if(userName ==NULL){
		strcpy (outMsg,"UserName can not be null\n");
		return -1;
	}
	result=inet_pton(addrType,ipAddress, &addr_ip);
	if(result==0){
		strcpy(outMsg , "ip address is invalid\n");
		return -1;
	}else if(result ==-1){
		strcpy(outMsg , "ip address Error\n");
		return -1;
	}
	if(ctrlPort <=0  || ctrlPort >=65535) {
		strcpy(outMsg,"Control Port error, must be 1-65534\n");
		return -1;
	}

	buf=(char*) malloc(10+strlen(userName)+strlen(secret));

	ptr=buf;

        *ptr=VERSION_CTRL; ptr++;
        *ptr=CMD_ADMIN; ptr++;
        *ptr=ADMIN_NBR; ptr++;
        *ptr=ADMIN_ADD; ptr++;
	
	*ptr=strlen(userName);
	ptr++;
	strcpy(ptr,userName);
	ptr+=strlen(userName);

	*ptr=strlen(secret);
	ptr++;
	strcpy(ptr,secret);
	ptr+=strlen(secret);

	*ptr=addrType;
	ptr++;

	memcpy(ptr, &addr_ip, addrLength(addrType));
	ptr+=addrLength(addrType);

	memcpy(ptr, &ctrlPort, 2);
	ptr+=2;

	ATOMIC_OUT(sock, buf, ptr-buf,"Send CMD_NBR_ADD Error in neighbor_add() \n");
                    
	memset(buf,0,10); 

	ATOMIC_IN(sock, buf, 3, "Receive CMD_NBR reply error in neighbor_add()\n");

        result=buf[2];
	
	free(buf);
	if(result ==0xFF){
		strcpy(outMsg,"Add Neighbor Error\n");
	}
	return result;
}
int neighbor_request(int sock, int cmd_types, int cmd_action){
	    
    PDU_HEADER ph;
    int bytes,leng; 
    unsigned char u_len,addrType,status; 
    char  userName[MAX_USERNAME],ipAddress[128],ipstr[128];
    unsigned short controlPort;
    struct node_item  * nip;
	
        ph.version=VERSION_CTRL;
        ph.command=CMD_NBR_REQUEST;

	ATOMIC_OUT(sock, &ph, sizeof(ph), "Erro send response header");

	u_len=0;
	atomic_out(sock, &u_len,1);

	ATOMIC_OUT(sock, &u_len,1, "Error send U_LEN in neighbor_request().\n");


	// read from server ...

	ATOMIC_IN(sock, &ph,sizeof(ph), "Error Read response header in neighbor_request().\n");

	ATOMIC_IN(sock, &u_len,1, "Error Read response header in neighbor_request().\n");
        while(u_len){
		memset(userName,0,sizeof(userName));

		ATOMIC_IN(sock,userName,u_len, "read userName Error in neighbor_request()\n");

		ATOMIC_IN(sock,&addrType,1, "read addrType in neighbor_request()\n");
		leng=addrLength(addrType);

		ATOMIC_IN(sock,&ipAddress,leng, "read ipAddress in neighbor_request()\n");

		ATOMIC_IN(sock,&controlPort,2, "read control port in neighbor_request()\n");
		inet_ntop(addrType,&ipAddress, ipstr, sizeof(ipstr));

		ATOMIC_IN(sock,&status,1, "read status in neighbor_request()\n");
		printf("%s:%s:%s:%d:%d\n",userName,lookup_code(address_type,addrType), 
					  ipstr, ntohs(controlPort), status);

		ATOMIC_IN(sock,&u_len,1, "read u_len in neighbor_request()\n");
        }
}


int authentication(int sock, char *userName, char *password)
{
	int status;
	PDU_HEADER ph;
	char pdu[1024], *ptr; //*userName,*password;
	int  length, bytes;
	unsigned char u_length, n_methods;
	unsigned char selectedMethod;
	//struct node_item *me;
	int succeed;
	
	//me=neighbors.first;
	
	ph.version=VERSION_CTRL;	
	ph.command=CMD_CLIENT_HELLO;	
	
	bytes=atomic_out(sock,&ph, sizeof(ph));
	if(bytes != sizeof(ph)){
		debug("send pdu header error  in authentication_exchange()\n");	
		return -1;
	}
	//userName=me->userName;

	//password=np->secret;

	ptr=pdu; length=0;
	succeed=0;	

	*ptr=(unsigned char) strlen(userName);
	ptr++;	
	strcpy(ptr,userName);
	ptr+=strlen(userName);

	n_methods=2;
	*ptr++=n_methods;
	*ptr++=METHOD_NONE;
	*ptr++=METHOD_PASS;

	length=ptr-pdu;
	bytes=atomic_out(sock, pdu,(ptr-pdu));
	if(bytes != length) {
		debug(" sent client_hello Error . authentication_exchange()\n");
	}	

	bytes=atomic_in(sock,&ph, sizeof(ph));
	if(bytes != sizeof(ph)) {
		debug(" recieve server_hello pdu header Error. authentication_exchange()\n");
		return -1;
	}	
	if(ph.command != CMD_SERVER_HELLO){
		debug(" CMD_SERVER_HELLO expected, but got:%d. authentication_exchange()\n",
			ph.command);
		return -1;
	}  
	bytes=atomic_in(sock,&selectedMethod, 1);
	if(bytes != 1) {
		debug(" recieve S_A_M Error. authentication_exchange()\n");
		return -1;
	}
	switch(selectedMethod){
	    case METHOD_NONE:
		 succeed=1;

	    break;
	    case METHOD_PASS:
        	ph.version=VERSION_CTRL;
        	ph.command=CMD_CLIENT_AUTH;
        	bytes=atomic_out(sock,&ph, sizeof(ph));
        	if(bytes != sizeof(ph)){
                	debug("send pdu header (password) error  in authentication_exchange()\n");
                	return -1;
        	}	

		memset(pdu,0,sizeof(pdu));
		ptr=pdu;
		*ptr++=strlen(password);
		strcpy(ptr,password);
		u_length=strlen(password)+1;	

        	bytes=atomic_out(sock,pdu,u_length);
        	if(bytes != u_length){
                	debug("send password error  in authentication_exchange()\n");
                	return -1;
        	}	
		
		// read CMD_SERVER_CONF 
		bytes=atomic_in(sock, &ph, sizeof(ph));
		if(bytes != sizeof(ph)){
			debug("read pdu header error (read CMD_SERVER_CONF)\n");
			return -1;
		}	

		if(ph.command ==CMD_SERVER_CONF) {
			debug("Authentication OK\n");	
		 	succeed=1;
		}
		else if(ph.command==CMD_SERVER_ERROR){
			debug("Authentication failed, password missmatch.\n");
		 	succeed=0;
		}
	    break;

	    case METHOD_X509:
	    case METHOD_CHAP:
	    default: 
		debug("Authentication Method Not supported now\n");
		succeed =0;
	    break;
	}
	
	if(succeed){
		//np->status=STATUS_AUTHED;	
		return 0;
	}else	return -1;
	
}

int get_services(int sock)
{
    PDU_HEADER ph;
    unsigned char  n_services,srvcType, tail;
    int bytes, length;
    struct node_item * me;
    char pdu[1024],*ptr;
    struct service_item * sp;
    unsigned short relayPort;

    debug("get service()\n");

    memset(pdu,0,sizeof(pdu));

    pdu[0]=VERSION_CTRL;
    pdu[1]=CMD_SRVC_UPDATE;
    pdu[2]=0; //srvcType;

    ATOMIC_OUT(sock,pdu, 3, "Send Service Request Error\n");

    ///////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////

    debug("waiting for server update...\n");

    ATOMIC_IN(sock,&ph, sizeof(ph), "Read service update reply error\n");

    if(ph.command != CMD_SRVC_ACCEPT){
            debug("CMD_SRVC_ACCEPT expected, but got:%d. service_update()\n",
                    ph.command);
            return -1;
    }

    ATOMIC_IN(sock,&srvcType,1,"Read Service Type Error\n");

    while(srvcType){
        ATOMIC_IN(sock,&relayPort,2, "Read RelayPort Error\n");
	printf("%s:%d\n", lookup_code(service_name,srvcType),
		ntohs(relayPort));			
        ATOMIC_IN(sock,&srvcType,1,"Read srvcType Error\n");
    }

    return 0;	
}

